Skip to content

1150 get local plans data into provide tool - #1159

Merged
pooleycodes merged 23 commits into
mainfrom
1150-get-local-plans-data-into-provide-tool
Mar 31, 2026
Merged

1150 get local plans data into provide tool#1159
pooleycodes merged 23 commits into
mainfrom
1150-get-local-plans-data-into-provide-tool

Conversation

@pooleycodes

@pooleycodes pooleycodes commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

🚨 When merging and pushing to production, make sure redis is cleared 🚨

Description

Extension to show banners for joint planning groups.

  • shows a banner in the dataset page when a planning group exists that has also been provisioned to provide.
  • shows a blue box in membership / members

Local plans / planning group banners

  • New _planning-group-notice.html partial — banner for LPAs in a joint planning group
  • Banner included on all major LPA views: overview, dataset-overview, task list, dataview, get-started
  • overview.html extended to show organisations breakdown and LPA members

Config

  • Added organisationType variable such to allow list of organisation parents by updated in config files and dependent on environment, so dev shows local planning groups, staging and production do not.

API / middleware

  • platformApi.js — new calls to fetch local plan / planning group data
  • common.middleware.js — major additions to fetch and attach planning group context to requests
  • schemas.js — expanded validation schemas

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Related Tickets & Documents

Tests

  • Added integration test checking the group membership api call correctly works with wiremock for the platform

Summary by CodeRabbit

  • New Features

    • Show local planning group membership and provisions via contextual banners and links on organisation overview, dataset overview, task list, data view and get‑started pages.
    • Reorganise organisation search/results by dataset type and surface per‑group counts.
  • Style

    • Reduced font size for large numeric displays for improved readability.
  • Chores

    • Added configuration entries for organisation types used in planning/group logic.
  • Tests

    • Added integration test for planning group display and updated related unit tests.

@pooleycodes pooleycodes linked an issue Mar 11, 2026 that may be closed by this pull request
4 tasks
@coderabbitai

coderabbitai Bot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

Added planning‑group support: new plan dataset entries in config; Platform API pagination and organisation helpers; middleware to fetch local planning groups and provisions; templates, schemas, filters and views updated to surface planning‑group data; tests added/updated; minor styling and dataset‑slug init changes.

Changes

Cohort / File(s) Summary
Configuration
config/default.yaml
Added plan-timetable, local-plan, minerals-plan, waste-plan dataset entries with guidanceUrl: /guidance/specifications/plan and entityDisplayName.variable: plan.
Styling
src/assets/scss/index.scss
Reduced .big-number font size from 80 → 48 (weight remains bold).
Platform API
src/services/platformApi.js
Made organisation_entity/dataset optional in fetchEntities; added prefix & organisation query support; added paginated fetchAllEntities and fetchOrganisations helpers.
Core middleware & queries
src/middleware/common.middleware.js
Include dataset in fetchOrgInfo; switched prepareAuthority to use fetchAllEntities for quality: 'some'; fixed fetchDatasetFields to use req.dataset.dataset; added fetchLocalPlanningGroups and fetchProvisionsByOrgsAndDatasets.
Middleware pipelines
src/middleware/...
datasetOverview.middleware.js, datasetTaskList.middleware.js, dataview.middleware.js, getStarted.middleware.js
Inserted fetchLocalPlanningGroups and fetchProvisionsByOrgsAndDatasets; surface planningGroupProvisions (filters out current LPA when appropriate) into req.templateParams.
Overview & organisations
src/middleware/lpa-overview.middleware.js, src/middleware/organisations.middleware.js
Overview pipeline runs fetchLocalPlanningGroups; templates receive parentGroup and planningGroupMembers; organisations SQL selects o.dataset and includes local-planning-group:%; listing grouping changed from alphabet → dataset.
Schemas
src/routes/schemas.js
Added optional dataset to OrgField; added PlanningGroupProvisionsField; renamed alphabetisedOrgsorgsByDataset; added parentGroup, planningGroupMembers, and planningGroupProvisions to affected page schemas.
Filters / utils
src/filters/filters.js, src/utils/datasetSlugToReadableName.js
Wrapped dataset slug filter for variadic forwarding; changed dataset slug init to retry indefinitely with logged delays until mapping succeeds.
Templates & includes
src/views/includes/_planning-group-notice.html, src/views/organisations/...
New _planning-group-notice.html include; included on dataset overview, task list, dataview, get‑started; find.html and overview.html updated to group by dataset and render planning‑group banners/members.
Tests
test/integration/planning_group.playwright.test.js, test/unit/*.test.js, test/integration/authoritative_data.playwright.test.js
Added Playwright test for planning‑group banner; updated unit tests/mocks to expect fetchAllEntities and orgsByDataset; improved WireMock reset error handling; minor test adjustments for new templates/data.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant Middleware as Middleware Pipeline
    participant PlatformAPI as Platform API
    participant Database as Database
    participant Template as Template Engine

    Client->>Middleware: GET /organisations/:lpa (with dataset)
    Middleware->>PlatformAPI: fetchLocalPlanningGroups(prefix=local-planning-group)
    PlatformAPI-->>Middleware: planning group entities
    alt need names
        Middleware->>PlatformAPI: fetchOrganisations(orgs=ids)
        PlatformAPI-->>Middleware: organisation names
    end
    Middleware->>Database: fetchProvisionsByOrgsAndDatasets(lpa + parentOrgs, dataset)
    Database-->>Middleware: provision rows (with org names)
    Middleware->>Middleware: compute planningGroupProvisions (exclude current LPA if multiple)
    Middleware->>Template: render view (includes planningGroupProvisions, parentGroup, planningGroupMembers)
    Template->>Client: HTML response (includes _planning-group-notice if provisions present)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • Ben-Hodgkiss
  • eveleighoj

Poem

🐰 I hopped through folders, near and far,
Found groups and plans and a guiding star.
Banners bloom where provisions rest,
Datasets sorted, nicely dressed,
A nimble hop — the pages cheer!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title refers to a core objective of the PR—getting local plans data into the provide tool—which aligns with the primary changes across config, middleware, API, UI and tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 1150-get-local-plans-data-into-provide-tool

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Mar 11, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 66.03% 6858 / 10385
🔵 Statements 66.03% 6858 / 10385
🔵 Functions 63.59% 276 / 434
🔵 Branches 78.05% 914 / 1171
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/filters/filters.js 100% 100% 100% 100%
src/middleware/common.middleware.js 64.13% 89.6% 35.93% 64.13% 31-40, 44, 58-78, 91-92, 94-95, 107-109, 113, 129-136, 150-154, 179-194, 209-210, 230-231, 253-267, 325-338, 344-366, 450-459, 475-488, 553, 562-564, 594-596, 644-685, 725-729, 734-737, 749-753, 781-795, 802-816, 822-830, 834-850, 919-921, 994-1001, 1016-1071, 1102-1103, 1106-1109, 1112-1125, 1150-1155, 1165-1168, 1177, 1194-1228, 1241-1250
src/middleware/datasetOverview.middleware.js 85.12% 54.54% 33.33% 85.12% 17-37, 79-83, 94-99, 136-138, 220
src/middleware/datasetTaskList.middleware.js 89.7% 65.38% 66.66% 89.7% 115-123, 141-142, 169-178
src/middleware/dataview.middleware.js 100% 52.94% 60% 100%
src/middleware/getStarted.middleware.js 100% 25% 100% 100%
src/middleware/lpa-overview.middleware.js 80.94% 74.39% 63.63% 80.94% 19-20, 62-64, 76-78, 84-86, 108-119, 136-138, 148-150, 199-200, 203-204, 259-267, 280, 282, 284, 340-343, 399-407, 420-431, 434-446
src/middleware/organisations.middleware.js 53.01% 100% 40% 53.01% 9-27, 34-44, 47-55
src/routes/schemas.js 100% 100% 100% 100%
src/services/platformApi.js 70.32% 53.33% 40% 70.32% 26-27, 64-84, 98-104, 108-123
src/utils/datasetSlugToReadableName.js 68.42% 75% 100% 68.42% 13-18
Generated in workflow #1367 for commit 06dca37 by the Vitest Coverage Report Action

@pooleycodes
pooleycodes marked this pull request as ready for review March 19, 2026 15:04

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/middleware/lpa-overview.middleware.js (1)

271-287: ⚠️ Potential issue | 🟠 Major

Keep totalDatasets aligned with the other overview counters.

prepareDatasetObjects() builds datasets from the full availableDatasets list, and datasetsWithEndpoints / datasetsWithIssues / datasetsWithErrors are still reduced over that full array. After this change, totalDatasets drops the other bucket, so the overview can end up showing more datasets with endpoints or issues than the reported total. Either include other here or calculate the other counters from the same filtered subset.

♻️ Minimal fix if the summary is meant to cover every dataset
-  const totalDatasets = (datasetsByReason.statutory?.length ?? 0) + (datasetsByReason.expected?.length ?? 0) + (datasetsByReason.prospective?.length ?? 0)
+  const totalDatasets =
+    (datasetsByReason.statutory?.length ?? 0) +
+    (datasetsByReason.expected?.length ?? 0) +
+    (datasetsByReason.prospective?.length ?? 0) +
+    (datasetsByReason.other?.length ?? 0)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/middleware/lpa-overview.middleware.js` around lines 271 - 287,
totalDatasets currently sums only statutory/expected/prospective from
datasetsByReason, causing it to mismatch the other counters
(datasetsWithEndpoints, datasetsWithIssues, datasetsWithErrors) which were
reduced over the full datasets array produced by prepareDatasetObjects /
availableDatasets using orgStatsReducer; update totalDatasets to include the
'other' bucket (i.e. include datasetsByReason.other?.length) or instead compute
totalDatasets from the same source as the other counters (e.g. use
datasets.length or the orgStatsReducer result) so all overview counters are
computed from the same filtered/complete set.
src/middleware/common.middleware.js (1)

215-230: ⚠️ Potential issue | 🟠 Major

Restore the non-authoritative record count.

After switching this branch to platformApi.fetchAllEntities(), someResult?.data?.count is always undefined, so req.entityCount never gets set for authority === 'some'. The later fallback then counts unfiltered Datasette rows instead of the filtered Platform API result, which can skew pagination and the record summary on these pages.

♻️ Suggested fix
     if (someResult.formattedData && someResult.formattedData.length > 0) {
       req.authority = 'some'
       // Set list of alternate entities provided in req for later use
       const rows = someResult.formattedData || []
       req.alternateEntityList = rows.map(({ entity }) => entity)
-      // Also set record count here if available
-      const someCount = someResult?.data?.count
-      if (someCount !== undefined) {
-        req.entityCount = { entity_count: someCount }
-      }
+      req.entityCount = { entity_count: rows.length }
     } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/middleware/common.middleware.js` around lines 215 - 230, The branch that
handles authority === 'some' now calls platformApi.fetchAllEntities, and
someResult?.data?.count is undefined so req.entityCount never gets set; update
the some-result handling in the someResult block (where
platformApi.fetchAllEntities is called and req.alternateEntityList is computed)
to restore the non-authoritative record count by falling back to a reliable
source: if someResult?.data?.count is undefined, set req.entityCount = {
entity_count: rows.length } (or use someResult?.count or someResult?.meta?.count
if present) so pagination/summary use the filtered Platform API result; keep the
existing assignments to req.authority and req.alternateEntityList.
🧹 Nitpick comments (5)
src/services/platformApi.js (2)

59-84: Incomplete JSDoc block and duplicated query construction.

The fetchDatasets JSDoc block (lines 51-58) is left open, and a new JSDoc block for fetchAllEntities is started immediately after on line 59. This creates malformed documentation.

Additionally, the query parameter construction logic (lines 70-75) duplicates what's in fetchEntities (lines 30-36). Consider extracting a helper function to reduce duplication.

♻️ Proposed fix for JSDoc and extraction suggestion
   * `@throws` {Error} If the query fails or there is an error communicating with the Platform API
   */
+
+  /**
+   * Fetches all entities from the Platform API /entity.json endpoint, paginating through all results.
+   * Accepts the same params as fetchEntities (except limit/offset which are managed internally).
+   *
+   * `@param` {Object} params - Query parameters (same as fetchEntities)
+   * `@returns` {Promise<{formattedData: object[]}>} - All entities across all pages
+   */
-  /**
-   * Fetches all entities from the Platform API /entity.json endpoint, paginating through all results.
-   * Accepts the same params as fetchEntities (except limit/offset which are managed internally).
-   */
   fetchAllEntities: async (params) => {

Consider extracting a helper to build query params:

function buildEntityQueryParams(params, limit, offset) {
  const queryParams = new URLSearchParams()
  if (params.organisation_entity) queryParams.append('organisation_entity', params.organisation_entity)
  if (params.dataset) queryParams.append('dataset', params.dataset)
  if (params.prefix) queryParams.append('prefix', params.prefix)
  if (params.organisation) queryParams.append('organisation', params.organisation)
  if (limit) queryParams.append('limit', limit)
  if (offset) queryParams.append('offset', offset)
  if (params.quality) queryParams.append('quality', params.quality)
  return queryParams
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/platformApi.js` around lines 59 - 84, The JSDoc for
fetchDatasets is left unclosed and fetchAllEntities repeats query-building logic
from fetchEntities; close/fix the JSDoc block for fetchDatasets and extract the
duplicated URLSearchParams logic into a helper (e.g., buildEntityQueryParams)
then use that helper from both fetchEntities and fetchAllEntities to append
organisation_entity, dataset, prefix, organisation and limit/offset; ensure
buildEntityQueryParams accepts params, limit, offset (and optional quality) and
returns a URLSearchParams instance, then update fetchAllEntities to call the
helper instead of duplicating the query construction.

68-81: Consider adding a safety limit to prevent infinite loops.

The pagination logic is correct for normal API behaviour. However, if the Platform API has a bug returning links.next indefinitely, this could cause an infinite loop. Consider adding a maximum iteration safeguard.

🛡️ Optional safeguard
   fetchAllEntities: async (params) => {
     const pageSize = 100
     let offset = 0
     let allEntities = []
+    const maxPages = 1000 // Safety limit

-    while (true) {
+    for (let page = 0; page < maxPages; page++) {
       const queryParams = new URLSearchParams()
       // ... existing code ...
       if (!data?.links?.next || entities.length < pageSize) break
       offset += pageSize
     }
+
+    if (allEntities.length >= maxPages * pageSize) {
+      logger.warn({ message: 'fetchAllEntities reached maximum page limit', type: types.DataFetch, params })
+    }

     return { formattedData: allEntities }
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/platformApi.js` around lines 68 - 81, The while(true) pagination
loop in src/services/platformApi.js that calls queryPlatformAPI should include a
maximum iteration safeguard (e.g., MAX_PAGES) to avoid infinite loops if
data?.links?.next is always present; modify the loop that builds queryParams and
fetches entities so it increments a page counter each iteration and breaks (or
throws/logs a clear error) when the counter exceeds MAX_PAGES, ensuring existing
logic that checks data?.links?.next and entities.length < pageSize still runs;
update any function signature or local variables if needed (e.g., add a
configurable maxPages constant near pageSize/offset) and surface a clear error
message mentioning queryPlatformAPI and the endpoint when the max is reached.
src/views/organisations/overview.html (1)

181-209: Consider adding fallbacks for member/group names.

In the _planning-group-notice.html partial, there's a fallback to provision.organisation when provision.name is missing. However, lines 189 and 203 here use member.name and group.name directly without fallbacks. If the name property is missing, this could render empty link text.

♻️ Proposed fix to add fallbacks
-            <li><a class="govuk-link" href="/organisations/{{ member.organisation }}">{{ member.name }}</a></li>
+            <li><a class="govuk-link" href="/organisations/{{ member.organisation }}">{{ member.name or member.organisation }}</a></li>
-            <li><a class="govuk-link" href="/organisations/{{ group.organisation }}">{{ group.name }}</a></li>
+            <li><a class="govuk-link" href="/organisations/{{ group.organisation }}">{{ group.name or group.organisation }}</a></li>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/views/organisations/overview.html` around lines 181 - 209, The template
renders member.name and group.name directly which can produce empty link text;
update the two link texts in the planningGroupMembers and parentGroup loops to
use a fallback (e.g., show member.name falling back to member.organisation, and
group.name falling back to group.organisation) using your template engine’s
default/or operator or filter so the <a> text is never empty (these are the <li>
entries inside the planningGroupMembers loop and the parentGroup loop in the
planning group block—match the fallback style used in
_planning-group-notice.html).
src/middleware/common.middleware.js (1)

1193-1227: Consider caching the planning-group metadata.

This middleware now sits on several page paths, and each request performs a full local-planning-group scan, plus an organisation lookup when the current org is itself a planning group. A short TTL cache for the group list and org-name map would trim latency and make the new banners less dependent on the Platform API on every page view.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/middleware/common.middleware.js` around lines 1193 - 1227, The middleware
fetchLocalPlanningGroups currently queries platformApi.fetchAllEntities and
platformApi.fetchOrganisations on every request causing unnecessary latency; add
a short TTL in-memory cache for the fetched local-planning-group list and the
organisation name map so subsequent calls reuse cached data until expiry.
Implement a module-level cache object (e.g., cachedGroups, cachedOrgNameMap,
cachedAt, ttlMs) used by fetchLocalPlanningGroups: check cache validity before
calling platformApi.fetchAllEntities/fetchOrganisations, populate the caches and
cachedAt after a successful fetch, and fall back to existing behavior on cache
miss or error; ensure the try/catch/error path still sets req.parentGroup and
req.planningGroupMembers and does not throw. Reference symbols:
fetchLocalPlanningGroups, platformApi.fetchAllEntities,
platformApi.fetchOrganisations, req.parentGroup, req.planningGroupMembers.
src/routes/schemas.js (1)

120-126: Consider making planningGroupProvisions consistently an array (not optional).

Using v.optional(v.array(...)) means templates and callers must always handle undefined. If this field is part of the standard page contract, make it required as an array (empty when no data).

Suggested refactor
-const PlanningGroupProvisionsField = v.optional(v.array(v.strictObject({
+const PlanningGroupProvisionsField = v.array(v.strictObject({
   organisation: NonEmptyString,
   name: v.nullable(v.string()),
   dataset: NonEmptyString,
   project: v.nullable(v.string()),
   provision_reason: v.nullable(v.string())
-})))
+}))

Also applies to: 175-175, 202-202, 221-221, 242-242

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/schemas.js` around lines 120 - 126, The schema currently defines
PlanningGroupProvisionsField as optional (v.optional(v.array(...))) which forces
callers to handle undefined; change it to a required array by replacing
v.optional(v.array(...)) with v.array(...) so the field is always an array
(empty when no items). Update the same pattern wherever planningGroupProvisions
is defined (the other occurrences referenced in the review) so all corresponding
schema constants use v.array(...) instead of v.optional(v.array(...)) to enforce
a consistent required-array contract; keep the inner item shape (organisation,
name, dataset, project, provision_reason) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/middleware/common.middleware.js`:
- Around line 1239-1250: The query in fetchProvisionsByOrgsAndDatasets builds
SQL by interpolating params.lpa, params.dataset and values from req.parentGroup
directly, creating an SQL injection risk; fix it by converting this into a
parameterised query (use prepared statement placeholders and pass orgs and
dataset as parameters) or, if parameterised SQL isn't supported here, properly
escape/quote each value (including those from req.parentGroup) using a safe
whitelist/quoting helper and avoid string concatenation; update the
query-building logic in fetchProvisionsByOrgsAndDatasets to use the safe
parameters/escaped values and ensure the IN clause is constructed from bound
parameters rather than raw interpolation.

In `@src/middleware/getStarted.middleware.js`:
- Around line 6-14: The planning-group notice is being suppressed by the
premature `provisions?.length > 1` guard; change the logic to first filter
provisions (use the existing variable planningGroupProvisions =
provisions?.filter(...) or similar) and then check the filtered array length to
decide whether to set planningGroupProvisions (i.e., set planningGroupProvisions
to the filtered array if filtered.length > 0, otherwise undefined). Update this
fix in the planningGroupProvisions builders found in getStarted.middleware.js
(planningGroupProvisions), dataview.middleware.js,
datasetOverview.middleware.js, and datasetTaskList.middleware.js so all use
filter-first-then-check semantics.

In `@src/middleware/organisations.middleware.js`:
- Around line 74-81: The middleware changed the template param key from
alphabetisedOrgs to orgsByDataset causing unit tests to fail; update
req.templateParams in the organisations middleware to use the original key
expected by tests (set req.templateParams = { alphabetisedOrgs: orgsByDataset }
or rename the variable) so tests referencing alphabetisedOrgs (and the strict
equality checks) pass, keeping the grouping logic in the reduce over
sortedResults intact (reference: orgsByDataset, sortedResults,
req.templateParams).

In `@src/routes/schemas.js`:
- Around line 156-160: parentGroup.entity is defined as NonEmptyString but
OrgField.entity is numeric and the platform returns numeric organisation-entity
IDs (they’re coerced with String() elsewhere), causing validation failures;
update both occurrences of parentGroup (the
v.optional(v.nullable(v.array(v.strictObject({...}))) ) definitions) to use the
same numeric type as OrgField.entity (i.e., change the entity field to the
numeric schema used by OrgField) so that parentGroup.entity accepts numeric IDs
from the middleware/API.

---

Outside diff comments:
In `@src/middleware/common.middleware.js`:
- Around line 215-230: The branch that handles authority === 'some' now calls
platformApi.fetchAllEntities, and someResult?.data?.count is undefined so
req.entityCount never gets set; update the some-result handling in the
someResult block (where platformApi.fetchAllEntities is called and
req.alternateEntityList is computed) to restore the non-authoritative record
count by falling back to a reliable source: if someResult?.data?.count is
undefined, set req.entityCount = { entity_count: rows.length } (or use
someResult?.count or someResult?.meta?.count if present) so pagination/summary
use the filtered Platform API result; keep the existing assignments to
req.authority and req.alternateEntityList.

In `@src/middleware/lpa-overview.middleware.js`:
- Around line 271-287: totalDatasets currently sums only
statutory/expected/prospective from datasetsByReason, causing it to mismatch the
other counters (datasetsWithEndpoints, datasetsWithIssues, datasetsWithErrors)
which were reduced over the full datasets array produced by
prepareDatasetObjects / availableDatasets using orgStatsReducer; update
totalDatasets to include the 'other' bucket (i.e. include
datasetsByReason.other?.length) or instead compute totalDatasets from the same
source as the other counters (e.g. use datasets.length or the orgStatsReducer
result) so all overview counters are computed from the same filtered/complete
set.

---

Nitpick comments:
In `@src/middleware/common.middleware.js`:
- Around line 1193-1227: The middleware fetchLocalPlanningGroups currently
queries platformApi.fetchAllEntities and platformApi.fetchOrganisations on every
request causing unnecessary latency; add a short TTL in-memory cache for the
fetched local-planning-group list and the organisation name map so subsequent
calls reuse cached data until expiry. Implement a module-level cache object
(e.g., cachedGroups, cachedOrgNameMap, cachedAt, ttlMs) used by
fetchLocalPlanningGroups: check cache validity before calling
platformApi.fetchAllEntities/fetchOrganisations, populate the caches and
cachedAt after a successful fetch, and fall back to existing behavior on cache
miss or error; ensure the try/catch/error path still sets req.parentGroup and
req.planningGroupMembers and does not throw. Reference symbols:
fetchLocalPlanningGroups, platformApi.fetchAllEntities,
platformApi.fetchOrganisations, req.parentGroup, req.planningGroupMembers.

In `@src/routes/schemas.js`:
- Around line 120-126: The schema currently defines PlanningGroupProvisionsField
as optional (v.optional(v.array(...))) which forces callers to handle undefined;
change it to a required array by replacing v.optional(v.array(...)) with
v.array(...) so the field is always an array (empty when no items). Update the
same pattern wherever planningGroupProvisions is defined (the other occurrences
referenced in the review) so all corresponding schema constants use v.array(...)
instead of v.optional(v.array(...)) to enforce a consistent required-array
contract; keep the inner item shape (organisation, name, dataset, project,
provision_reason) unchanged.

In `@src/services/platformApi.js`:
- Around line 59-84: The JSDoc for fetchDatasets is left unclosed and
fetchAllEntities repeats query-building logic from fetchEntities; close/fix the
JSDoc block for fetchDatasets and extract the duplicated URLSearchParams logic
into a helper (e.g., buildEntityQueryParams) then use that helper from both
fetchEntities and fetchAllEntities to append organisation_entity, dataset,
prefix, organisation and limit/offset; ensure buildEntityQueryParams accepts
params, limit, offset (and optional quality) and returns a URLSearchParams
instance, then update fetchAllEntities to call the helper instead of duplicating
the query construction.
- Around line 68-81: The while(true) pagination loop in
src/services/platformApi.js that calls queryPlatformAPI should include a maximum
iteration safeguard (e.g., MAX_PAGES) to avoid infinite loops if
data?.links?.next is always present; modify the loop that builds queryParams and
fetches entities so it increments a page counter each iteration and breaks (or
throws/logs a clear error) when the counter exceeds MAX_PAGES, ensuring existing
logic that checks data?.links?.next and entities.length < pageSize still runs;
update any function signature or local variables if needed (e.g., add a
configurable maxPages constant near pageSize/offset) and surface a clear error
message mentioning queryPlatformAPI and the endpoint when the max is reached.

In `@src/views/organisations/overview.html`:
- Around line 181-209: The template renders member.name and group.name directly
which can produce empty link text; update the two link texts in the
planningGroupMembers and parentGroup loops to use a fallback (e.g., show
member.name falling back to member.organisation, and group.name falling back to
group.organisation) using your template engine’s default/or operator or filter
so the <a> text is never empty (these are the <li> entries inside the
planningGroupMembers loop and the parentGroup loop in the planning group
block—match the fallback style used in _planning-group-notice.html).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0fe0d727-13ea-47d7-be28-fccc06489ab6

📥 Commits

Reviewing files that changed from the base of the PR and between 671d467 and 920f728.

📒 Files selected for processing (18)
  • config/default.yaml
  • src/assets/scss/index.scss
  • src/middleware/common.middleware.js
  • src/middleware/datasetOverview.middleware.js
  • src/middleware/datasetTaskList.middleware.js
  • src/middleware/dataview.middleware.js
  • src/middleware/getStarted.middleware.js
  • src/middleware/lpa-overview.middleware.js
  • src/middleware/organisations.middleware.js
  • src/routes/schemas.js
  • src/services/platformApi.js
  • src/views/includes/_planning-group-notice.html
  • src/views/organisations/dataset-overview.html
  • src/views/organisations/datasetTaskList.html
  • src/views/organisations/dataview.html
  • src/views/organisations/find.html
  • src/views/organisations/get-started.html
  • src/views/organisations/overview.html

Comment thread src/middleware/common.middleware.js
Comment thread src/middleware/getStarted.middleware.js
Comment thread src/middleware/organisations.middleware.js
Comment thread src/routes/schemas.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
test/integration/planning_group.playwright.test.js (1)

27-27: Tighten the WireMock URL regex.

Line 27 uses entity.json with an unescaped dot, so it matches more than the literal path and can mask unrelated requests.

Suggested fix
-      urlPattern: '/entity.json.*prefix=local-planning-group.*'
+      urlPattern: '/entity\\.json.*prefix=local-planning-group.*'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/integration/planning_group.playwright.test.js` at line 27, The WireMock
urlPattern currently uses an unescaped dot in the string assigned to urlPattern
(the '/entity.json.*prefix=local-planning-group.*' pattern), which allows
unintended matches; update the urlPattern to escape the literal dot (use
'entity\\.json') and tighten the regex (e.g., anchor the query separator or
start of pattern) so it matches the exact path and expected query parameter
structure; modify the urlPattern assignment accordingly to use the corrected
regex.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/integration/planning_group.playwright.test.js`:
- Around line 17-19: Tests in this file call resetWiremock() inside
test.beforeEach which races with other concurrently running suites
(fullyParallel: true) and can clear stubs from other workers; to fix, make the
suite run serially by adding a describe block and calling
test.describe.configure({ mode: 'serial' }) (or otherwise ensure this file is
executed serially) so resetWiremock() (and any test.beforeEach) cannot interfere
with other concurrent tests; locate test.beforeEach and replace/enclose tests in
a test.describe with test.describe.configure({ mode: 'serial' }) to enforce
serial execution.
- Around line 3-15: The WireMock admin helper functions resetWiremock and
addWiremockStub don't validate HTTP responses; modify both to capture the fetch
response, check response.ok, and throw a clear error if not ok (including status
and response text) so failures in POSTing to /__admin/mappings or
/__admin/mappings/reset are detected immediately and tests fail fast.

---

Nitpick comments:
In `@test/integration/planning_group.playwright.test.js`:
- Line 27: The WireMock urlPattern currently uses an unescaped dot in the string
assigned to urlPattern (the '/entity.json.*prefix=local-planning-group.*'
pattern), which allows unintended matches; update the urlPattern to escape the
literal dot (use 'entity\\.json') and tighten the regex (e.g., anchor the query
separator or start of pattern) so it matches the exact path and expected query
parameter structure; modify the urlPattern assignment accordingly to use the
corrected regex.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4663f015-0184-4cf6-ab30-dbf42bdba373

📥 Commits

Reviewing files that changed from the base of the PR and between 920f728 and b1f0b79.

📒 Files selected for processing (5)
  • src/views/organisations/find.html
  • test/integration/planning_group.playwright.test.js
  • test/unit/findPage.test.js
  • test/unit/middleware/common.middleware.test.js
  • test/unit/middleware/organisations.middleware.test.js

Comment thread test/integration/planning_group.playwright.test.js
Comment thread test/integration/planning_group.playwright.test.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/routes/schemas.js (2)

118-118: Tighten OrgField.dataset to NonEmptyString.

Now that OrgFindPage is keyed by dataset, allowing dataset: '' inside OrgField is looser than both DatasetNameField.dataset and the new orgsByDataset record key. Reusing NonEmptyString here keeps the dataset contract consistent.

♻️ Proposed change
-const OrgField = v.strictObject({ name: NonEmptyString, organisation: NonEmptyString, statistical_geography: v.optional(v.string()), entity: v.optional(v.integer()), dataset: v.optional(v.string()) })
+const OrgField = v.strictObject({ name: NonEmptyString, organisation: NonEmptyString, statistical_geography: v.optional(v.string()), entity: v.optional(v.integer()), dataset: v.optional(NonEmptyString) })

Also applies to: 168-168

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/schemas.js` at line 118, OrgField currently allows dataset:
v.optional(v.string()) which is too permissive; change the dataset schema in the
OrgField definition to use NonEmptyString (e.g., dataset:
v.optional(NonEmptyString)) so it matches DatasetNameField.dataset and the
orgsByDataset record key; update the OrgField declaration (and the other
occurrence at the second instance mentioned) to replace v.optional(v.string())
with v.optional(NonEmptyString).

155-164: Extract the repeated planning-group object schemas.

These additions make sense, but parentGroup is duplicated verbatim across both overview schemas, and planningGroupMembers is another inline strict object. Pulling them into shared constants would make future field changes less error-prone.

♻️ Proposed refactor
+const ParentGroupField = v.optional(v.nullable(v.array(v.strictObject({
+  entity: v.integer(),
+  name: NonEmptyString,
+  organisation: NonEmptyString
+}))))
+
+const PlanningGroupMembersField = v.optional(v.nullable(v.array(v.strictObject({
+  organisation: NonEmptyString,
+  name: NonEmptyString
+}))))
+
 export const OrgOverviewPage = v.strictObject({
   organisation: OrgField,
   datasets: v.object({
@@
   datasetsWithErrors: v.integer(),
   isODPMember: v.boolean(),
-  parentGroup: v.optional(v.nullable(v.array(v.strictObject({
-    entity: v.integer(),
-    name: NonEmptyString,
-    organisation: NonEmptyString
-  })))),
-  planningGroupMembers: v.optional(v.nullable(v.array(v.strictObject({
-    organisation: NonEmptyString,
-    name: NonEmptyString
-  }))))
+  parentGroup: ParentGroupField,
+  planningGroupMembers: PlanningGroupMembersField
 })
@@
   }),
   planningGroupProvisions: PlanningGroupProvisionsField,
-  parentGroup: v.optional(v.nullable(v.array(v.strictObject({
-    entity: v.integer(),
-    name: NonEmptyString,
-    organisation: NonEmptyString
-  })))),
+  parentGroup: ParentGroupField,
   notice: v.optional(DeadlineNoticeField)
 })

Also applies to: 203-207

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/schemas.js` around lines 155 - 164, The parentGroup and
planningGroupMembers object schemas are duplicated inline; extract them into
shared schema constants (e.g., ParentGroupSchema and PlanningGroupMemberSchema)
defined once and reuse them where parentGroup and planningGroupMembers are
declared: ParentGroupSchema should be v.strictObject({ entity: v.integer(),
name: NonEmptyString, organisation: NonEmptyString }) and
PlanningGroupMemberSchema should be v.strictObject({ organisation:
NonEmptyString, name: NonEmptyString }); then replace the inline
v.array(v.strictObject(...)) usages with v.array(ParentGroupSchema) and
v.array(PlanningGroupMemberSchema) wrapped in the same
v.optional(v.nullable(...)) as before so both overview schemas reference the
single shared definitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/routes/schemas.js`:
- Line 118: OrgField currently allows dataset: v.optional(v.string()) which is
too permissive; change the dataset schema in the OrgField definition to use
NonEmptyString (e.g., dataset: v.optional(NonEmptyString)) so it matches
DatasetNameField.dataset and the orgsByDataset record key; update the OrgField
declaration (and the other occurrence at the second instance mentioned) to
replace v.optional(v.string()) with v.optional(NonEmptyString).
- Around line 155-164: The parentGroup and planningGroupMembers object schemas
are duplicated inline; extract them into shared schema constants (e.g.,
ParentGroupSchema and PlanningGroupMemberSchema) defined once and reuse them
where parentGroup and planningGroupMembers are declared: ParentGroupSchema
should be v.strictObject({ entity: v.integer(), name: NonEmptyString,
organisation: NonEmptyString }) and PlanningGroupMemberSchema should be
v.strictObject({ organisation: NonEmptyString, name: NonEmptyString }); then
replace the inline v.array(v.strictObject(...)) usages with
v.array(ParentGroupSchema) and v.array(PlanningGroupMemberSchema) wrapped in the
same v.optional(v.nullable(...)) as before so both overview schemas reference
the single shared definitions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bded4493-b1a4-4992-a78e-d704648bf16e

📥 Commits

Reviewing files that changed from the base of the PR and between b1f0b79 and 79dc595.

📒 Files selected for processing (2)
  • src/routes/schemas.js
  • src/services/platformApi.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/platformApi.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/utils/datasetSlugToReadableName.js (2)

21-24: The fire-and-forget pattern is intentional and sound, but consider adding observability.

The non-awaited call to retryUntilLoaded() allows the server to start immediately with graceful degradation. This aligns with the PR objective of continuous retry on boot failure.

However, there's currently no way to know whether the mapping has successfully loaded (e.g., for health checks or operational monitoring). Consider exposing a status indicator.

💡 Optional: Add a flag to indicate mapping load status
 let datasetSlugToReadableName = (slug) => slug
+let mappingLoaded = false

 const retryUntilLoaded = async () => {
   while (true) {
     try {
       const mapping = await getDatasetSlugNameMapping()
       datasetSlugToReadableName = makeDatasetSlugToReadableNameFilter(mapping)
+      mappingLoaded = true
       return
     } catch (error) {
       // ...
     }
   }
 }

+const isMappingLoaded = () => mappingLoaded

 export {
   datasetSlugToReadableName,
-  initDatasetSlugToReadableNameFilter
+  initDatasetSlugToReadableNameFilter,
+  isMappingLoaded
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/datasetSlugToReadableName.js` around lines 21 - 24, The
initDatasetSlugToReadableNameFilter currently calls retryUntilLoaded()
fire-and-forget, so add observable state: introduce a boolean flag (e.g.,
isDatasetMappingLoaded) initialized false and set it true inside
retryUntilLoaded (or its success callback), export a getter function (e.g.,
isMappingLoaded or getDatasetMappingStatus) alongside datasetSlugToReadableName,
and update retryUntilLoaded to update that flag on success and optionally expose
a promise (e.g., mappingLoadedPromise) for health checks to await; reference
initDatasetSlugToReadableNameFilter, retryUntilLoaded, and
datasetSlugToReadableName when making these changes.

14-16: Use the structured logger instead of console.error for consistency.

The codebase uses structured logging elsewhere (e.g., logger.error() in src/utils/datasetteQueries/fetchDatasetCollections.js). Using console.error here bypasses any structured logging configuration, log aggregation, or filtering set up for the application.

♻️ Suggested change
+import logger from '../utils/logger.js'
+import { types } from '../utils/logging.js'

 // ...

     } catch (error) {
-      console.error(`Failed to load dataset mapping, retrying in ${RETRY_INTERVAL_MS / 1000}s:`, error)
+      logger.error({
+        message: `Failed to load dataset mapping, retrying in ${RETRY_INTERVAL_MS / 1000}s`,
+        error,
+        type: types.DataFetch
+      })
       await new Promise(resolve => setTimeout(resolve, RETRY_INTERVAL_MS))
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/datasetSlugToReadableName.js` around lines 14 - 16, Replace the
console.error in the catch block of datasetSlugToReadableName.js with the
project's structured logger: call logger.error(...) passing a clear message
(e.g., "Failed to load dataset mapping, retrying") plus the error object and
include RETRY_INTERVAL_MS for context; if logger isn't imported in that module,
add the same logger import used elsewhere (matching other files like
fetchDatasetCollections.js) so the structured logger is available, then keep the
existing await new Promise(...) retry logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/utils/datasetSlugToReadableName.js`:
- Around line 21-24: The initDatasetSlugToReadableNameFilter currently calls
retryUntilLoaded() fire-and-forget, so add observable state: introduce a boolean
flag (e.g., isDatasetMappingLoaded) initialized false and set it true inside
retryUntilLoaded (or its success callback), export a getter function (e.g.,
isMappingLoaded or getDatasetMappingStatus) alongside datasetSlugToReadableName,
and update retryUntilLoaded to update that flag on success and optionally expose
a promise (e.g., mappingLoadedPromise) for health checks to await; reference
initDatasetSlugToReadableNameFilter, retryUntilLoaded, and
datasetSlugToReadableName when making these changes.
- Around line 14-16: Replace the console.error in the catch block of
datasetSlugToReadableName.js with the project's structured logger: call
logger.error(...) passing a clear message (e.g., "Failed to load dataset
mapping, retrying") plus the error object and include RETRY_INTERVAL_MS for
context; if logger isn't imported in that module, add the same logger import
used elsewhere (matching other files like fetchDatasetCollections.js) so the
structured logger is available, then keep the existing await new Promise(...)
retry logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c97448da-0f31-4eb9-909a-ada6530a696d

📥 Commits

Reviewing files that changed from the base of the PR and between 5996306 and 3d937fa.

📒 Files selected for processing (2)
  • src/filters/filters.js
  • src/utils/datasetSlugToReadableName.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/utils/datasetSlugToReadableName.js (1)

9-20: Retry loop implementation is sound, but consider adding a maximum retry count or backoff.

The infinite retry with fixed 30-second intervals will eventually succeed, but consider:

  1. Adding exponential backoff to reduce load on a struggling upstream service
  2. Logging the attempt count for observability
  3. Optionally capping retries and failing loudly if the service is fundamentally unavailable
♻️ Optional: add attempt counter for observability
 const retryUntilLoaded = async () => {
+  let attempt = 0
   while (true) {
+    attempt++
     try {
       const mapping = await getDatasetSlugNameMapping()
       datasetSlugToReadableName = makeDatasetSlugToReadableNameFilter(mapping)
+      logger.info(`Dataset mapping loaded successfully after ${attempt} attempt(s)`)
       return
     } catch (error) {
-      logger.warn(`Failed to load dataset mapping, retrying in ${RETRY_INTERVAL_MS / 1000}s:`, error)
+      logger.warn(`Failed to load dataset mapping (attempt ${attempt}), retrying in ${RETRY_INTERVAL_MS / 1000}s:`, error)
       await new Promise(resolve => setTimeout(resolve, RETRY_INTERVAL_MS))
     }
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/datasetSlugToReadableName.js` around lines 9 - 20, The
retryUntilLoaded loop should be made bounded and backoff-aware: add an attempt
counter and a maxAttempts constant, log the attempt number in logger.warn,
implement exponential backoff based on RETRY_INTERVAL_MS (e.g., wait =
RETRY_INTERVAL_MS * 2**(attempt-1) with a cap) before the next
getDatasetSlugNameMapping try, and after exceeding maxAttempts either throw the
error or call a failure handler so the process fails loudly; update references
inside retryUntilLoaded and keep makeDatasetSlugToReadableNameFilter and
getDatasetSlugNameMapping usage unchanged.
test/testContainers/localstack.js (1)

16-20: Consider adding a comment to explain the testcontainers reuse pattern.

The stop() method's logic—creating and "starting" a container only to immediately stop it—is intentional for testcontainers with .withReuse(true) (which returns the existing running container), but it reads as counterintuitive. A brief comment would help future maintainers understand this behaviour.

📝 Suggested clarifying comment
 async stop () {
   console.log('Stopping LocalstackContainer')
+  // With reuse enabled, .start() returns the already-running container rather than creating a new one.
   this.container = await new LocalstackContainer(this.image).withReuse(true).start()
   await this.container.stop()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/testContainers/localstack.js` around lines 16 - 20, The stop()
implementation is intentionally creating a new LocalstackContainer and calling
.withReuse(true).start() to obtain the already-running reusable container before
calling stop(), but this is not obvious; add a brief inline comment inside the
stop() method (next to the this.container = await new
LocalstackContainer(this.image).withReuse(true).start() line) explaining that
withReuse(true) returns the existing running container so we start to attach to
it and then call this.container.stop() to stop the reused instance, clarifying
the counterintuitive flow for future maintainers (reference: stop(),
LocalstackContainer, withReuse(true)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/datasetSlugToReadableName.js`:
- Around line 22-25: initDatasetSlugToReadableNameFilter currently calls
retryUntilLoaded() without awaiting so it returns datasetSlugToReadableName
immediately (causing raw slugs to be used); change it to await the first
successful load from retryUntilLoaded (or modify retryUntilLoaded to return a
promise that resolves on first success) before returning
datasetSlugToReadableName so callers (like the awaited import in index.js) get
the populated mapping; if you intentionally want non-blocking startup instead,
add a clear comment in initDatasetSlugToReadableNameFilter explaining the
fire-and-forget behavior and shorten the initial retry interval or implement a
synchronous fallback cache.

---

Nitpick comments:
In `@src/utils/datasetSlugToReadableName.js`:
- Around line 9-20: The retryUntilLoaded loop should be made bounded and
backoff-aware: add an attempt counter and a maxAttempts constant, log the
attempt number in logger.warn, implement exponential backoff based on
RETRY_INTERVAL_MS (e.g., wait = RETRY_INTERVAL_MS * 2**(attempt-1) with a cap)
before the next getDatasetSlugNameMapping try, and after exceeding maxAttempts
either throw the error or call a failure handler so the process fails loudly;
update references inside retryUntilLoaded and keep
makeDatasetSlugToReadableNameFilter and getDatasetSlugNameMapping usage
unchanged.

In `@test/testContainers/localstack.js`:
- Around line 16-20: The stop() implementation is intentionally creating a new
LocalstackContainer and calling .withReuse(true).start() to obtain the
already-running reusable container before calling stop(), but this is not
obvious; add a brief inline comment inside the stop() method (next to the
this.container = await new
LocalstackContainer(this.image).withReuse(true).start() line) explaining that
withReuse(true) returns the existing running container so we start to attach to
it and then call this.container.stop() to stop the reused instance, clarifying
the counterintuitive flow for future maintainers (reference: stop(),
LocalstackContainer, withReuse(true)).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d3501184-34ff-4d0c-905c-44d72f202601

📥 Commits

Reviewing files that changed from the base of the PR and between 5996306 and 06dca37.

📒 Files selected for processing (12)
  • .github/workflows/test.yml
  • config/default.yaml
  • config/production.yaml
  • config/staging.yaml
  • src/filters/filters.js
  • src/middleware/organisations.middleware.js
  • src/utils/datasetSlugToReadableName.js
  • test/integration/global-setup.js
  • test/integration/global-teardown.js
  • test/testContainers/localstack.js
  • test/testContainers/wiremock.js
  • test/unit/views/organisations/get-startedPage.test.js
✅ Files skipped from review due to trivial changes (2)
  • config/production.yaml
  • config/staging.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/filters/filters.js
  • src/middleware/organisations.middleware.js
  • config/default.yaml

Comment thread src/utils/datasetSlugToReadableName.js
@pooleycodes
pooleycodes requested a review from gibahjoe March 31, 2026 15:28

@gibahjoe gibahjoe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@pooleycodes
pooleycodes merged commit 143a4db into main Mar 31, 2026
5 checks passed
@pooleycodes
pooleycodes deleted the 1150-get-local-plans-data-into-provide-tool branch March 31, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Get Local Plans data into Provide tool

3 participants